package
{
	import flare.animate.Parallel;
	import flare.animate.Sequence;
	import flare.animate.TransitionEvent;
	import flare.display.RectSprite;
	import flare.util.Colors;
	
	import flash.events.Event;

	public class Canvas extends RectSprite
	{
		public static const DEFAULT_WIDTH:Number = 500.0;
		public static const DEFAULT_HEIGHT:Number = 500.0;
		public static const DEFAULT_X_OFFSET:Number = 0.0;
		public static const DEFAULT_Y_OFFSET:Number = 0.0;
		
		public static const divisionDepth:Number = 2;
		
		public var rootRect:DividedRectangle;
		public var zoomQueue:Array;
		
		public var isZooming:Boolean;
		public var notifyControllerZoom:Boolean;
		
		public var data:DataManager;
		public var controller:TreeMapControls;
		
		public var highlightRect:RectSprite;
		public var highlightedRect:DividedRectangle;
		
		public var collapseCallback:Function;
		public var collapseTarget:Object;
		
		public var numCollapsesLeft:Number;
		public var numExpansionsLeft:Number;
		public var theSentence:Sentence;
		
		public function Canvas(theData:DataManager, x:Number=DEFAULT_X_OFFSET, y:Number=DEFAULT_Y_OFFSET, w:Number=DEFAULT_WIDTH, h:Number=DEFAULT_HEIGHT, sentence:Sentence = null)
		{
			super();
			theSentence = sentence;
			isZooming = false;
			zoomQueue = new Array();
			highlightRect = new RectSprite();
			highlightRect.fillColor = Colors.rgba(255, 255, 255, 40);
			
			this.data = theData;
			
			this.w = w;
        	this.h = h;
        	this.x = x;
        	this.y = y;
			
        	this.fillColor = Colors.rgba(100, 100, 200, 255);
        	this.lineColor = Colors.rgba(0, 0, 0, 255);
        	this.lineWidth = 1.0;
        	
        	rootRect = new DividedRectangle(this.w, this.h, 5.0, 3.0, this, null, {name:"Entire Population", value:"Entire Population", count:data.totalDatums()});
        	rootRect.fillColor = this.fillColor;
        	rootRect.lineColor = Colors.rgba(0, 0, 0, 255);
        	rootRect.lineWidth = 2.0;
        	
        	this.addChild(rootRect);
        	
		}
		
		public function sentenceForRect(theRect:DividedRectangle):String
		{
			if(theRect.nameValueCount == null || theRect.parentRect == null)
			{
				return "";
			}
			var lineage:String = theRect.parentString();
			var result:String = theRect.stringForRightLabel() + " of donors"
			
			if(lineage.length > 0)
			{
				result += " with " + lineage;
			}
			
			result += " have " + theRect.nameValueCount.name + " = " + theRect.nameValueCount.value + ".";
			
			return result;
		}
		
		public function addNameToResults(results:Array, theName:String):Array
		{
			var result:Array = new Array();
			
			for(var i:Number = 0; i < results.length; i++)
			{
				var nameValue:Object = results[i];
				var nameValueCount:Object = {name:theName, value:nameValue.name, count:nameValue.value};
				result.push(nameValueCount);
			}
			
			return result;
		}
		
		public function numCurrentDivisions():Number
		{
			var initialRect:DividedRectangle = topRect();
			var result:Number = 0;
			while(initialRect.hasAnyData())
			{
				initialRect = initialRect.fullRects[0];
				result++;
			}
			
			return result;
		}
		
		public function potentialDivisionsFromShelf(theData:ShelfData):Array
		{
			var allUndivided:Array = theData.unassignedArray();
			var result:Array = new Array();
			
			for(var i:Number = 0; i < allUndivided.length; i++)
			{
				if(i < divisionDepth)
				{
					result.push(allUndivided[i]);
				}
			}
			
			return result;
		}
		
		public function getLowestUndividedRectsInRect(theRect:DividedRectangle):Array
		{
			var initialRect:DividedRectangle = theRect;
			if(!initialRect.hasAnyData())
			{
				return new Array(initialRect);
			}
			
			var result:Array = new Array();
			
			if(initialRect.hasAnyData())
			{
				for(var i:Number = 0; i < initialRect.fullRects.length; i++)
				{
					var childResult:Array = this.getLowestUndividedRectsInRect(initialRect.fullRects[i]);
					for(var j:Number = 0; j < childResult.length; j++)
					{
						result.push(childResult[j]);
					}
				}
			}
			
			return result;
		}
		
		public function getLowestDividedRectsInRect(theRect:DividedRectangle):Array
		{
			var initialRect:DividedRectangle = theRect;
			if(initialRect.hasAnyData() && !initialRect.areChildrenDivided())
			{
				return new Array(initialRect);
			}
			
			var result:Array = new Array();
			
			if(initialRect.areChildrenDivided())
			{
				for(var i:Number = 0; i < initialRect.fullRects.length; i++)
				{
					var childResult:Array = this.getLowestDividedRectsInRect(initialRect.fullRects[i]);
					for(var j:Number = 0; j < childResult.length; j++)
					{
						result.push(childResult[j]);
					}
				}
			}
			
			return result;
		}
		
		public function expandOn(field:String, expandTarget:Object = null, expandNotify:Function = null):void
		{
				var toExpand:Array = this.getLowestUndividedRectsInRect(topRect());
				var theNewData:Array = new Array();
				
				for(var j:Number = 0 ; j < toExpand.length; j++)//gather data
				{
					var thisRect:DividedRectangle = toExpand[j];
					
					var whereClause:Array = controller.shelfData().whereArray();
					if(thisRect.nameValueCount != null)
					{
						whereClause.push({name:thisRect.nameValueCount.name, value:thisRect.nameValueCount.value});
					}
					
					theNewData.push(data.query(field, whereClause));
				}
				
				var everything:Parallel = new Parallel();
				
				for(var k:Number = 0 ; k < toExpand.length; k++)//spawn animations
				{
					var thatRect:DividedRectangle = toExpand[k];
					var transition:Sequence = thatRect.adjustToData(theNewData[k]);
					everything.add(transition);
				}
				
				if(expandTarget != null && expandNotify != null)
				{
					everything.addEventListener(TransitionEvent.END, expandNotify);
				}
				
				everything.play();
				
		}
		
		public function collapseFinished(evt:Event):void
		{
			if(collapseCallback != null && collapseTarget != null)
			{
				collapseCallback.call(collapseTarget);
			}
		}
		
		public function collapseLowest(notifyTarget:Object = null, notifyFunction:Function = null):void
		{
			collapseCallback = notifyFunction;
			collapseTarget = notifyTarget;
			
			var toCollapse:Array = this.getLowestDividedRectsInRect(topRect());
			var collapse:Parallel = new Parallel();
				
			for(var l:Number = 0 ; l < toCollapse.length; l++)
			{
				collapse.add(toCollapse[l].reform());
			}
			
			if(notifyFunction != null && notifyTarget != null)
			{
				collapse.addEventListener(TransitionEvent.END, this.collapseFinished);
			}
				
			collapse.play();
		}
		
		private function expandChain(evt:TransitionEvent):void
		{
			numExpansionsLeft--;
			
			if(numExpansionsLeft > 0)
			{
				var newDivisions:Array = this.potentialDivisionsFromShelf(controller.shelfData());
				this.expandOn(newDivisions[newDivisions.length - numExpansionsLeft], this, this.expandChain);
			}
		}
		
		private function collapseChain():void
		{
			this.numCollapsesLeft--;
			
			if(numCollapsesLeft > 0)
			{
				this.collapseLowest(this, this.collapseChain);
			}else{
				var newDivisions:Array = this.potentialDivisionsFromShelf(controller.shelfData());
				this.expandOn(newDivisions[newDivisions.length - numExpansionsLeft], this, this.expandChain);
			}
		}
		
		public function variablesChanged(oldData:ShelfData, newData:ShelfData):void
		{
			var currentZoom:Number = zoomQueue.length;
			var oldNames:Array = oldData.getNames();
			var newNames:Array = newData.getNames();
			var currentDivisions:Array = this.potentialDivisionsFromShelf(oldData);
			var newDivisions:Array = this.potentialDivisionsFromShelf(newData);
			
			if(newNames.length == 0)//removed all
			{
				rootRect.reform().play();
				return;
			}

			var index:Number = 0;
			var rearranged:Boolean = false;
			
			for(index; index < currentDivisions.length && index < newDivisions.length; index++)
			{
				if(currentDivisions[index] != newDivisions[index])
				{
					rearranged = true;
					break;
				}
			}
			
			if(!rearranged)
			{
				if(currentDivisions.length < newDivisions.length)
				{
					this.expandOn(newDivisions[newDivisions.length - 1]);
				}else if(currentDivisions.length > newDivisions.length)
				{
					this.collapseLowest();
				}
			}else{
				var numCollapses:Number = currentDivisions.length - index;
				this.numCollapsesLeft = numCollapses;
				this.numExpansionsLeft = Math.min(newData.unassignedArray().length, newDivisions.length) - index;
				this.collapseLowest(this, this.collapseChain);
			}
		}
		
		public function shrinkTopRect():void
		{
			var theRect:DividedRectangle = zoomQueue.pop();
			theRect.addShiftCallback(this, this.zoomOutEnded);
			theRect.shiftToRect(theRect.previousRect, 1.0);
			if(controller != null && notifyControllerZoom)
			{
				controller.zoomOut();
			}
		}
		
		public function zoomOut(notifyController:Boolean = true):void
		{
			notifyControllerZoom = notifyController; 
			if(!isZooming)
			{
				if(zoomQueue.length > 0)
				{
					isZooming = true;
					
					var theRect:DividedRectangle = topRect();

					if(theRect.areChildrenDivided())
					{
						this.collapseLowest(this, this.shrinkTopRect);
					}else{
						this.shrinkTopRect();
					}				
				}
			}
		}
		
		public function zoomInEnded():void
		{
			var topRect:DividedRectangle = zoomQueue[zoomQueue.length - 1];
			topRect.removeShiftCallback(this, this.zoomInEnded);
			
			var currentData:ShelfData = this.controller.shelfData();
			var newDivisions:Array = this.potentialDivisionsFromShelf(currentData);
			var nCurrentDivisions:Number = this.numCurrentDivisions();
			
			if(nCurrentDivisions < newDivisions.length)
			{
				this.expandOn(newDivisions[newDivisions.length - 1]);
			}
			
			isZooming = false;
		}
		
		public function zoomOutEnded():void
		{
			isZooming = false;
		}
		
		public function zoomToRect(theRect:DividedRectangle):void
		{
			if(!isZooming)
			{
				isZooming = true;
				zoomQueue.push(theRect);
				theRect.addShiftCallback(this, this.zoomInEnded);
				theRect.shiftToRect(new Rectangle(0, 0, this.w, this.h), 1.0);
				
				if(theRect.nameValueCount != null && controller != null)
				{
					controller.zoomIn(theRect.nameValueCount.value);
				}
			}
		}
		
		public function topRect():DividedRectangle
		{
			if(zoomQueue.length > 0)
			{
				return zoomQueue[zoomQueue.length - 1];
			}
			
			return rootRect;
		}
		
		public function mouseOutRect(theRect:DividedRectangle):void
		{
			trace("mouse UP/out");
			
			if(highlightRect != null)
			{
				if(this.contains(highlightRect))
				{
					this.removeChild(highlightRect);
				}
			}
			
			highlightedRect = null;
		}
		
		public function updateSentenceForRect(theRect:DividedRectangle):void
		{
			theSentence.updateText(this.sentenceForRect(theRect));
		}
		
		public function mouseOverRect(theRect:DividedRectangle):void
		{
			trace("mouse DOWN");
			if(theRect != rootRect)
			{
				if(highlightedRect == null)
				{
					highlightedRect = theRect;
				}
				
				if(theRect.rank() >= highlightedRect.rank())
				{
					highlightedRect = theRect;
					
					var totalOffset:Point = theRect.totalOffset();
					
					highlightRect.x = totalOffset.x;
					highlightRect.y = totalOffset.y;
					highlightRect.w = theRect.w;
					highlightRect.h = theRect.h;
					
					this.addChild(highlightRect);
					
					this.updateSentenceForRect(theRect);
				}
				
			}
		}
		
		public function rectGotClicked(theRect:DividedRectangle):void
		{
			if(theRect == rootRect)
			{
				return;
			}
			
			if(zoomQueue.length > 0)
			{
				if(theRect == topRect())
				{
					zoomOut();
					return;
				}
			}
			
			if(theRect.parentRect == topRect())
			{
				zoomToRect(theRect);
			}
		}
		
		
		public function rectsDidSplit():void
		{
			rootRect.removeCallbackForSplitEnd(this, this.rectsDidSplit);
		}
		
	}
}